﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace System.IO
{
    public abstract class AbstractFileInfo : AbstractFileSystemInfo
    {
        #region Async

        public abstract void CreateAsync(Action<Stream, Exception> callback);

        public abstract void OpenReadAsync(Action<Stream, Exception> callback);

        public abstract void OpenTextAsync(Action<StreamReader, Exception> callback);

        public abstract void OpenWriteAsync(Action<Stream, Exception> callback);

        public abstract void OpenAsync(FileMode mode, FileAccess access, FileShare share, Action<Stream, Exception> callback);

        public abstract void OpenAsync(FileMode mode, FileAccess access, Action<Stream, Exception> callback);

        public abstract void OpenAsync(FileMode mode, Action<Stream, Exception> callback);

        public abstract void MoveToAsync(string destFileName, Action<Exception> callback);

        public abstract void CreateTextAsync(Action<StreamWriter, Exception> callback);

        #endregion Async

        public abstract Stream Create();

        public abstract Stream OpenRead();

        public abstract StreamReader OpenText();

        public abstract Stream OpenWrite();

        public abstract Stream Open(FileMode mode, FileAccess access, FileShare share);

        public abstract Stream Open(FileMode mode, FileAccess access);

        public abstract Stream Open(FileMode mode);

        public abstract void MoveTo(string destFileName);

        public abstract StreamWriter CreateText();

        #region CopyTo Other File System

        protected virtual void copyViaStreams(Stream reader, Stream writer, Action<Exception> errorCallback)
        {
            try
            {
                reader.CopyTo(writer, 4096);
                errorCallback(null);
            }
            catch (Exception exP)
            {
                errorCallback(new Exception("Error while copying data", exP));
            }
            finally
            {
                if (reader != null)
                {
                    reader.Close();
                    reader = null;
                }
                if (writer != null)
                {
                    writer.Close();
                    writer = null;
                }
            }
            errorCallback(null);
        }

        private static void CopyToWhenDestSupportsSyncOnly(Stream readStream, AbstractFileInfo dest, bool overwrite, Action<Exception> errorCallback)
        {
            Stream ws = null;
            if (overwrite && dest.Exists)
            {
                errorCallback(new Exception("Destination already exists"));
                return;
            }
            try
            {
                try
                {
                    ws = dest.Create();
                    try
                    {
                        readStream.CopyTo(ws, 4096);
                        errorCallback(null);
                    }
                    catch (Exception exP)
                    {
                        errorCallback(new Exception("Error while copying data", exP));
                    }
                }
                catch (Exception exOpenDW)
                {
                    errorCallback(new Exception("Error while open destination file", exOpenDW));
                }
            }
            catch (Exception exDS)
            {
                errorCallback(new Exception("Error while writing file", exDS));
            }
            finally
            {
                if (readStream != null)
                {
                    readStream.Close();
                    readStream = null;
                }
                if (ws != null)
                {
                    ws.Close();
                    ws = null;
                }
            }
        }

        public virtual void CopyToFileSystem(AbstractFileInfo dest, Action<Exception> errorCallback)
        {
            CopyToFileSystem(dest, true, errorCallback);
        }

        public virtual void CopyToFileSystem(AbstractFileInfo dest, bool overwrite, Action<Exception> errorCallback)
        {
            if (!dest.SupportsCreates)
            {
                errorCallback(new IOException("Destination FileSystem does not support creating files."));
            }

            Action<Stream, Stream, Exception> WriteOpenAsyncAction = (readStream, writeStream, exW) =>
            {
                if (exW != null)
                {
                    if (readStream != null)
                    {
                        readStream.Close();
                        readStream = null;
                    }
                    if (writeStream != null)
                    {
                        writeStream.Close();
                        writeStream = null;
                    }
                    errorCallback(new Exception("Error while writing file", exW));
                }
                else
                {
                    copyViaStreams(readStream, writeStream, errorCallback);
                }
            };

            if (this.SupportsAsync)
            {
                this.OpenReadAsync((readStream, ex) =>
                    {
                        if (ex != null)
                        {
                            if (readStream != null)
                            {
                                readStream.Close();
                                readStream = null;
                            }
                            errorCallback(new Exception("Error while reading file", ex));
                        }
                        else
                        {
                            if (dest.SupportsAsync)
                            {
                                dest.LoadInfo((success) =>
                                {
                                    // Ignoring flag assuming it will return the proper value
                                    if (!overwrite || !dest.Exists)
                                    {
                                        dest.CreateAsync((writeStream, exW) => WriteOpenAsyncAction(readStream, writeStream, exW));
                                    }
                                });
                            }
                            else
                            {
                                // Dest supports Sync only
                                CopyToWhenDestSupportsSyncOnly(readStream, dest, overwrite, errorCallback);
                            }
                        }
                    });
            }
            else
            {
                // Synchronous read
                Stream sr = null;
                try
                {
                    sr = OpenRead();
                    if (dest.SupportsAsync)
                    {
                        dest.LoadInfo((success) =>
                        {
                            // Ignoring flag assuming it will return the proper value
                            if (!overwrite || !dest.Exists)
                            {
                                dest.CreateAsync((writeStream, exW) => WriteOpenAsyncAction(sr, writeStream, exW));
                            }
                        });
                    }
                    else
                    {
                        // Dest supports Sync only
                        CopyToWhenDestSupportsSyncOnly(sr, dest, overwrite, errorCallback);
                    }
                }
                catch (Exception exSR)
                {
                    errorCallback(new Exception("Error while reading file", exSR));
                }
                finally
                {
                    if (sr != null)
                    {
                        sr.Close();
                        sr = null;
                    }
                }
            }
        }

        #endregion CopyTo Other File System

        #region MoveToOtherFileSystem

        public virtual void MoveToFileSystem(AbstractFileInfo file, Action<Exception> errorCallback)
        {
            MoveToFileSystem(file, true, errorCallback);
        }

        public virtual void MoveToFileSystem(AbstractFileInfo dest, bool overwrite, Action<Exception> errorCallback)
        {
            if (!this.SupportsDeletes)
            {
                errorCallback(new IOException("Source does not support deletes (so you can't move a file)"));
            }
            CopyToFileSystem(dest, overwrite, (err) =>
            {
                if (err != null)
                {
                    errorCallback(err);
                    return;
                }
                if (SupportsAsync)
                {
                    DeleteAsync((ex) =>
                    {
                        if (ex == null)
                        {
                            errorCallback(null);
                        }
                        else
                        {
                            errorCallback(new Exception("Error deleting source file", ex));
                        }
                    });
                }
                else
                {
                    try
                    {
                        Delete();
                        errorCallback(null);
                    }
                    catch (Exception ex)
                    {
                        errorCallback(new Exception("Error deleting source file", ex));
                    }
                }
            });
        }

        #endregion MoveToOtherFileSystem

        public abstract AbstractFileInfo CopyTo(string destFileName, bool overwrite);

        public abstract AbstractFileInfo CopyTo(string destFileName);

        public abstract StreamWriter AppendText();

        public abstract long Length { get; }

        public abstract string DirectoryName { get; }

        public abstract AbstractDirectoryInfo Directory { get; }
    }
}